﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 * 
 ********************************************************/

using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using EffiProz.Core;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Results;
using EffiProz.Core.Navigators;
using EffiProz.Core.Errors;

namespace System.Data.EffiProz
{
    public class EfzDataReader : DbDataReader
    {
        /// <summary>
        /// Underlying command this reader is attached to
        /// </summary>
        private EfzCommand _command;

        /** The internal representation. */
        private Result _rResult=null;

        private Result _resultIn=null;

        /**
         * The current record containing the data for the row
         */
        private RowSetNavigator _nCurrent;

        /** The row upon which this ResultSet is currently positioned. */
        private int iCurrentRow;

        /** When the result of updating the database, the number of updated rows. */
        private int iUpdateCount = -1;

        /** Is current row before the first row? */
        private bool bInit;    // false if before first row

        /** How many columns does this ResultSet have? */
        int iColumnCount;

        /// <summary>
        /// State of the current statement being processed.
        /// -1 = First Step() executed, so the first Read() will be ignored
        ///  0 = Actively reading
        ///  1 = Finished reading
        ///  2 = Non-row-returning statement, no records
        /// </summary>
        private int _readingState = -1;

        /** The ResultSetMetaData object for this ResultSet */
        // private ResultSetMetaData rsmd;

        /** Properties of this ResultSet's parent Connection. */
        // private HsqlProperties connProperties;

        /** is the connection via network */
        // private bool isNetConn;

        internal long _version; // Matches the version of the connection

        /// <summary>
        /// The behavior of the datareader
        /// </summary>
        private CommandBehavior _commandBehavior;

        /// <summary>
        /// If set, then dispose of the command object when the reader is finished
        /// </summary>
        internal bool _disposeCommand;


        /** Used by this statement to communicate non-batched requests. */
        protected Result resultOut = Result.newExecuteDirectRequest();

        /** Use by this statement to communicate batched execution requests */
        protected Result batchResultOut = null;

        /** The currently existing generated key Result */
        Result _generatedResult = null;

       
        /// <summary>
        /// Internal constructor, initializes the datareader and sets up to begin executing statements
        /// </summary>
        /// <param name="cmd">The EffiProzCommand this data reader is for</param>
        /// <param name="behave">The expected behavior of the data reader</param>
        internal EfzDataReader(EfzCommand cmd, CommandBehavior behave)
        {
            _command = cmd;
            _version = _command.Connection.Version;

            _commandBehavior = behave;
            iUpdateCount = -1;

            if (_command != null)
                NextResult();
        }


        internal void Cancel()
        {
            _version = 0;
        }



        /// <summary>
        /// Closes the datareader, potentially closing the connection as well if CommandBehavior.CloseConnection was specified.
        /// </summary>
        public override void Close()
        {
            iUpdateCount = -1;


            if (_command != null)
            {
                try
                {
                    try
                    {
                        // Make sure we've not been canceled
                        if (_version != 0)
                        {
                            try
                            {
                                while (NextResult())
                                {
                                }
                            }
                            catch (EfzException)
                            {
                            }
                        }
                        _command.ClearDataReader();
                    }
                    finally
                    {
                        // If the datareader's behavior includes closing the connection, then do so here.
                        if ((_commandBehavior & CommandBehavior.CloseConnection) != 0 && _command.Connection != null)
                            _command.Connection.Close();
                    }
                }
                finally
                {
                    if (_disposeCommand)
                        _command.Dispose();
                }

                _command = null;
                _rResult = null;
                _resultIn = null;
                _readingState = -1;
            }
        }      


        /// <summary>
        /// Throw an error if the datareader is closed
        /// </summary>
        private void CheckClosed()
        {
            if (_command == null)
                throw new InvalidOperationException("DataReader has been closed");

            if (_version == 0)
                throw new EfzException("Execution was aborted by the user");

            if (_command.Connection.State != ConnectionState.Open || _command.Connection.Version != _version)
                throw new InvalidOperationException("Connection was closed, statement was terminated");
        }

        /// <summary>
        /// Throw an error if a row is not loaded
        /// </summary>
        private void CheckValidRow()
        {


            if (_readingState != 0) //if (rResult == null || !bInit || nCurrent == null)
            {
                throw EfzException.GetException(Error.error(ErrorCode.X_24501));
            }
        }

        /// <summary>
        /// Enumerator support
        /// </summary>
        /// <returns>Returns a DbEnumerator object.</returns>
        public override Collections.IEnumerator GetEnumerator()
        {
            return new DbEnumerator(this, ((_commandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection));
        }

        /// <summary>
        /// Not implemented.  Returns 0
        /// </summary>
        public override int Depth
        {
            get
            {
                CheckClosed();
                return 0;
            }
        }

        /// <summary>
        /// Returns the number of columns in the current resultset
        /// </summary>
        public override int FieldCount
        {
            get
            {
                CheckClosed();

                return iColumnCount;
            }
        }

        /// <summary>
        /// Returns the number of visible fielsd in the current resultset
        /// </summary>
        public override int VisibleFieldCount
        {
            get
            {
                CheckClosed();
                return iColumnCount;
            }
        }

        private object GetRawValue(int i)
        {
            CheckClosed();
            CheckValidRow();
            object[] values = _nCurrent.getCurrent();
            return values[i];
        }


        private Object getColumnInType(int columnIndex, SqlType targetType)
        {

            Object[] rowData = _nCurrent.getCurrent();
            SqlType sourceType;
            Object value;

            CheckClosed();
            CheckValidRow();

            sourceType = _rResult.metaData.columnTypes[columnIndex];
            value = rowData[columnIndex];

            if (value == null)
            {
                return null;
            }

            if (sourceType.typeCode != targetType.typeCode)
            {
                try
                {
                    value = targetType.convertToTypeAdo(_command.Connection.InnerConnection.SessionProxy,
                        value, sourceType);
                }
                catch (Exception)
                {
                    try
                    {
                        value = sourceType.convertToTypeAdo(_command.Connection.InnerConnection.SessionProxy,
                            value, targetType);
                        return value;
                    }
                    catch (Exception)
                    {
                    }

                    String msg = "from SQL type " + sourceType.getNameString()
                                 + " to " + targetType.getCSharpClassName()
                                 + ", value: " + value.ToString();

                    EfzException.ThrowError(Error.error(ErrorCode.X_42561, msg));
                }
            }

            return value;
        }


        /// <summary>
        /// Retrieves the column as a boolean value
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>bool</returns>
        public override bool GetBoolean(int i)
        {
            CheckNull(i);
            object val = getColumnInType(i, SqlType.SQL_BOOLEAN);
            if (val == null)
                throw new InvalidCastException();
            return (bool)val;
        }

        /// <summary>
        /// Retrieves the column as a single byte value
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>byte</returns>
        public override byte GetByte(int i)
        {
            CheckNull(i);
            object val = getColumnInType(i, SqlType.TINYINT);
            if (val == null)
                throw new InvalidCastException();
            return (byte)(int)val;
        }

        /// <summary>
        /// Retrieves a column as an array of bytes (blob)
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <param name="fieldOffset">The zero-based index of where to begin reading the data</param>
        /// <param name="buffer">The buffer to write the bytes into</param>
        /// <param name="bufferoffset">The zero-based index of where to begin writing into the array</param>
        /// <param name="length">The number of bytes to retrieve</param>
        /// <returns>The actual number of bytes written into the array</returns>
        /// <remarks>
        /// To determine the number of bytes in the column, pass a null value for the buffer.  The total length will be returned.
        /// </remarks>
        public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            CheckNull(i);

            if (_rResult.metaData.columnTypes[i].typeCode == Types.SQL_BLOB)
            {

                object val = getColumnInType(i, _rResult.metaData.columnTypes[i]);
                if (val is BlobDataID)
                {
                    BlobDataID b = (BlobDataID)val;
                    SessionInterface session = _command.Connection.InnerConnection.SessionProxy;
                    long blobLength = b.length(session);
                    if (fieldOffset > blobLength)
                        return 0;
                    length = length > (blobLength - fieldOffset) ? (int)(blobLength - fieldOffset) : length;
                    if (length <= 0)
                        return 0;
                    byte[] bytes = b.getBytes(session, fieldOffset, length);

                    Array.Copy(bytes, 0, buffer, bufferoffset, bytes.Length);

                    return bytes.Length;
                }
                else
                    throw new InvalidCastException();
            }
            else
            {
                Object x = getColumnInType(i, SqlType.SQL_VARBINARY);
                if (x == null)
                {
                    return 0;
                }

                byte[] bytes = ((BinaryData)x).getBytes();

                if (fieldOffset > bytes.Length)
                    return 0;

                length = length > bytes.Length ? bytes.Length : length;
                if (length < 0)
                    length = 0;
#if !PLATFORM_COMPACTFRAMEWORK                
                Array.Copy(bytes, fieldOffset, buffer, bufferoffset, length);
#else
                Array.Copy(bytes, (int)fieldOffset, buffer, bufferoffset, length);
#endif

                return length;
            }

            throw new InvalidCastException();
        }


        /// <summary>
        /// Returns the column as a single character
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>char</returns>
        public override char GetChar(int i)
        {
            CheckNull(i);
            object val = getColumnInType(i, SqlType.SQL_CHAR);
            if (val == null)
                throw new InvalidCastException();
            return (char)val;
        }

        /// <summary>
        /// Retrieves a column as an array of chars (blob)
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <param name="fieldoffset">The zero-based index of where to begin reading the data</param>
        /// <param name="buffer">The buffer to write the characters into</param>
        /// <param name="bufferoffset">The zero-based index of where to begin writing into the array</param>
        /// <param name="length">The number of bytes to retrieve</param>
        /// <returns>The actual number of characters written into the array</returns>
        /// <remarks>
        /// To determine the number of characters in the column, pass a null value for the buffer.  The total length will be returned.
        /// </remarks>
        public override long GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length)
        {
            CheckNull(i);

            if (_rResult.metaData.columnTypes[i].typeCode == Types.SQL_CLOB)
            {
                SqlType sourceType = _rResult.metaData.columnTypes[i];

                Object val = getColumnInType(i, sourceType);

                if (val == null)
                {
                    return 0;
                }

                if (val is ClobDataID)
                {
                    ClobDataID c = (ClobDataID)val;
                    SessionInterface session = _command.Connection.InnerConnection.SessionProxy;
                    long clobLength = c.length(session);
                    if (fieldOffset > clobLength)
                        return 0;
                    length = length > (clobLength - fieldOffset) ? (int)(clobLength - fieldOffset) : length;
                    if (length <= 0)
                        return 0;
                    char[] chars = c.getChars(session, fieldOffset, length);

                    Array.Copy(chars, 0, buffer, bufferoffset, chars.Length);

                    return chars.Length;
                }
                else
                    throw new InvalidCastException();
            }
            else
            {
                string val = (string)getColumnInType(i, SqlType.SQL_VARCHAR);

                if (val == null)
                {
                    return 0;
                }

                if (fieldOffset > val.Length)
                    return 0;
                length = length > (val.Length - fieldOffset) ? length : (val.Length - (int)fieldOffset);
                if (length <= 0)
                    return 0;

                val.CopyTo((int)fieldOffset, buffer, bufferoffset, length);

                return length;
            }
        }

        /// <summary>
        /// Retrieves the name of the back-end datatype of the column
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>string</returns>
        public override string GetDataTypeName(int i)
        {     
            CheckClosed();
            CheckValidRow();
            int internalType = _rResult.metaData.columnTypes[i].typeCode;
            return Types.getTypeName(internalType);
        }

        /// <summary>
        /// Retrieve the column as a date/time value
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>DateTime</returns>
        public override DateTime GetDateTime(int i)
        {
            CheckNull(i);

            SqlType sqlType = _rResult.metaData.columnTypes[i];
            SessionInterface session = _command.Connection.InnerConnection.SessionProxy;

            switch (sqlType.typeCode)
            {
                case Types.SQL_TIME:
                    {
                        TimeData t = (TimeData)getColumnInType(i, SqlType.SQL_TIME);
                        if (t == null)
                            throw new InvalidCastException(); ;
                        return (DateTime)SqlType.SQL_TIME.convertSQLToCSharp(session, t);
                    }
                case Types.SQL_TIME_WITH_TIME_ZONE:
                    {
                        TimeData t = (TimeData)getColumnInType(i, SqlType.SQL_TIME_WITH_TIME_ZONE);
                        if (t == null)
                            throw new InvalidCastException(); ;
                        return (DateTime)SqlType.SQL_TIME_WITH_TIME_ZONE.convertSQLToCSharp(session, t);
                    }
                case Types.SQL_DATE:
                    {
                        TimestampData t = (TimestampData)getColumnInType(i, SqlType.SQL_DATE);
                        if (t == null)
                            throw new InvalidCastException(); ;
                        return (DateTime)SqlType.SQL_DATE.convertSQLToCSharp(session, t);
                    }
                case Types.SQL_TIMESTAMP:
                    {
                        TimestampData t = (TimestampData)getColumnInType(i, SqlType.SQL_TIMESTAMP);
                        if (t == null)
                            throw new InvalidCastException(); ;
                        return (DateTime)SqlType.SQL_TIMESTAMP.convertSQLToCSharp(session, t);
                    }
                case Types.SQL_TIMESTAMP_WITH_TIME_ZONE:
                    {
                        TimestampData t = (TimestampData)getColumnInType(i, SqlType.SQL_TIMESTAMP_WITH_TIME_ZONE);
                        if (t == null)
                            throw new InvalidCastException(); ;
                        return (DateTime)SqlType.SQL_TIMESTAMP_WITH_TIME_ZONE.convertSQLToCSharp(session, t);
                    }
                default:
                    throw new InvalidCastException();
            }

        }

        /// <summary>
        /// Retrieve the column as a decimal value
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>decimal</returns>
        public override decimal GetDecimal(int i)
        {
            CheckNull(i);

            SqlType targetType = _rResult.metaData.columnTypes[i];

            if (targetType.typeCode != Types.SQL_NUMERIC &&
                targetType.typeCode != Types.SQL_DECIMAL)
            {
                targetType = SqlType.SQL_DECIMAL_DEFAULT;
            }

            object val = getColumnInType(i, targetType);
            if (val == null)
                throw new InvalidCastException();
            return (decimal)val;
        }

        /// <summary>
        /// Returns the column as a double
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>double</returns>
        public override double GetDouble(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_DOUBLE);
            if (val == null)
                throw new InvalidCastException();
            return (double)val;
        }

        /// <summary>
        /// Returns the .NET type of a given column
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>Type</returns>
        public override Type GetFieldType(int i)
        {
            CheckClosed();
            int internalType = _rResult.metaData.columnTypes[i].typeCode;

            switch (internalType)
            {
                case Types.SQL_BOOLEAN:
                    return typeof(bool);

                case Types.TINYINT:
                    return typeof(byte);

                case Types.SQL_SMALLINT:
                    return typeof(short);

                case Types.SQL_INTEGER:
                    return typeof(int);

                case Types.SQL_BIGINT:
                    return typeof(long);

                case Types.SQL_FLOAT:
                    return typeof(float);

                case Types.SQL_DOUBLE:
                case Types.SQL_REAL:
                    return typeof(double);

                case Types.SQL_DECIMAL:
                case Types.SQL_NUMERIC:
                    return typeof(decimal);

                case Types.SQL_VARCHAR:
                case Types.VARCHAR_IGNORECASE:
                case Types.SQL_CHAR:
                    return typeof(string);

                case Types.SQL_DATE:
                case Types.SQL_TIME:
                case Types.SQL_TIMESTAMP:
                    return typeof(DateTime);

                case Types.SQL_BINARY:
                case Types.SQL_VARBINARY:
                case Types.SQL_LONGVARBINARY:
                case Types.SQL_BLOB:
                    return typeof(byte[]);

                case Types.SQL_CLOB:
                    return typeof(byte[]);

                case Types.OTHER:
                    return typeof(object);

            }

            throw new InvalidCastException();

        }

        /// <summary>
        /// Returns a column as a float value
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>float</returns>
        public override float GetFloat(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_DOUBLE);
            if (val == null)
                throw new InvalidCastException();
            return (float)(double)val;
        }

        /// <summary>
        /// Returns the column as a Guid
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>Guid</returns>
        public override Guid GetGuid(int i)
        {
            throw new InvalidCastException();
        }

        /// <summary>
        /// Returns the column as a short
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>Int16</returns>
        public override Int16 GetInt16(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_SMALLINT);
            if (val == null)
                throw new InvalidCastException();
            return (short)(int)val;
        }

        /// <summary>
        /// Retrieves the column as an int
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>Int32</returns>
        public override Int32 GetInt32(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_INTEGER);
            if (val == null)
                throw new InvalidCastException();
            return (int)val;
        }

        /// <summary>
        /// Retrieves the column as a long
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>Int64</returns>
        public override Int64 GetInt64(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_BIGINT);
            if (val == null)
                throw new InvalidCastException();
            return (long)val;
        }

        /// <summary>
        /// Retrieves the name of the column
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>string</returns>
        public override string GetName(int columnIndex)
        {
            string name;
            try
            {
                name = _rResult.metaData.columnLabels[columnIndex];

                if(String.IsNullOrEmpty(name))
                    name = _rResult.metaData.columns[columnIndex].getOriginalNameString();
            }
            catch (IndexOutOfRangeException)
            {
                throw EfzException.GetException(ErrorCode.ADO_COLUMN_NOT_FOUND, (columnIndex).ToString());
            }

            return name;
        }

        /// <summary>
        /// Retrieves the i of a column, given its name
        /// </summary>
        /// <param name="name">The name of the column to retrieve</param>
        /// <returns>The int i of the column</returns>
        public override int GetOrdinal(string columnName)
        {
            CheckClosed();

            for (int i = 0; i < iColumnCount; i++)
            {
                String name = GetName(i);

                if (columnName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
                {
                    return i;
                }
            }

            throw new IndexOutOfRangeException(columnName);
        }

      

        public override DataTable GetSchemaTable()
        {
            CheckClosed();

            if (_rResult == null || (!_rResult.isData() && 
                (_commandBehavior & CommandBehavior.SchemaOnly) == 0 ))
            {
                throw EfzException.GetException(ErrorCode.GENERAL_ERROR);
            }

            DataTable tbl = new DataTable("SchemaTable");
            DataRow row;

            ResultMetaData rmd = _rResult.metaData;
            SqlType type;

            tbl.Locale = CultureInfo.InvariantCulture;
            tbl.Columns.Add(SchemaTableColumn.ColumnName, typeof(String));
            tbl.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
            tbl.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
            tbl.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(int));
            tbl.Columns.Add(SchemaTableColumn.NumericScale, typeof(short));
            tbl.Columns.Add(SchemaTableColumn.IsUnique, typeof(Boolean));
            tbl.Columns.Add(SchemaTableColumn.IsKey, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
            tbl.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(String));
            tbl.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(String));
            tbl.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(String));
            tbl.Columns.Add(SchemaTableColumn.BaseTableName, typeof(String));
            tbl.Columns.Add(SchemaTableColumn.DataType, typeof(Type));
            tbl.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(Boolean));
            tbl.Columns.Add(SchemaTableColumn.ProviderType, typeof(int));
            tbl.Columns.Add(SchemaTableColumn.IsAliased, typeof(Boolean));
            tbl.Columns.Add(SchemaTableColumn.IsExpression, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(Boolean));
            tbl.Columns.Add(SchemaTableColumn.IsLong, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(Boolean));
            tbl.Columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
            tbl.Columns.Add(SchemaTableOptionalColumn.DefaultValue, typeof(string));


            DataTable indexTbl = null;
            DataTable columnTbl = null;

            if ((_commandBehavior & CommandBehavior.KeyInfo) != 0) // provide all  available information
            {//Anyway this key Info will be invalid for multi-table select... just use first column table/schema

                indexTbl = _command.Connection.GetSchema("INDEXCOLUMNS", new string[] {null, rmd.columns[0].getOriginalSchemaNameString(), rmd.columns[0].getOriginalTableNameString(),
                    null,null,"true" });             
                columnTbl = _command.Connection.GetSchema("COLUMNS", new string[] { null, rmd.columns[0].getOriginalSchemaNameString(), rmd.columns[0].getOriginalTableNameString() });
            }


            tbl.BeginLoadData();

            for (int n = 0; n < rmd.getColumnCount(); n++)
            {
                row = tbl.NewRow();

                // Default settings for the column
                if (rmd.columnLabels[n] == null && String.IsNullOrEmpty(rmd.columns[n].getOriginalNameString()))
                    row[SchemaTableColumn.ColumnName] = DBNull.Value;
                else
                    row[SchemaTableColumn.ColumnName] = rmd.columnLabels[n] ?? rmd.columns[n].getOriginalNameString();

                row[SchemaTableColumn.ColumnOrdinal] = n;
                row[SchemaTableColumn.ColumnSize] = rmd.columnTypes[n].precision;
                row[SchemaTableColumn.ProviderType] = rmd.columnTypes[n].getAdoTypeCode();
                type = rmd.columnTypes[n];

                if (type.isNumberType())
                {
                    if (type.acceptsPrecision())
                    {
                        row[SchemaTableColumn.NumericPrecision] = rmd.columnTypes[n].precision;

                    }
                    else
                    {
                        row[SchemaTableColumn.NumericPrecision] = DBNull.Value;
                    }

                    if (type.acceptsScale())
                    {
                        row[SchemaTableColumn.NumericScale] = type.scale;
                    }
                    else
                        row[SchemaTableColumn.NumericScale] = DBNull.Value;
                }

                row[SchemaTableColumn.IsLong] = type.isLobType();

                row[SchemaTableColumn.AllowDBNull] = rmd.columns[n].getNullability() 
                                                            != SchemaObjectNullability.NO_NULLS;
                row[SchemaTableOptionalColumn.IsReadOnly] = !rmd.columns[n].isWriteable();
                row[SchemaTableOptionalColumn.IsAutoIncrement] = rmd.columns[n].isIdentity();
                row[SchemaTableOptionalColumn.IsHidden] = false;
                row[SchemaTableOptionalColumn.IsRowVersion] = false;

                row[SchemaTableColumn.IsUnique] = false;
                row[SchemaTableColumn.IsKey] = false;
                row[SchemaTableColumn.DataType] = GetFieldType(n);


                if (String.IsNullOrEmpty(rmd.columns[n].getOriginalNameString()))
                    row[SchemaTableColumn.BaseColumnName] = DBNull.Value;
                else
                    row[SchemaTableColumn.BaseColumnName] = rmd.columns[n].getOriginalNameString();

                row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(rmd.columns[n].getOriginalNameString());

                row[SchemaTableColumn.IsAliased] = (String.Compare(rmd.columns[n].getOriginalNameString(), rmd.columnLabels[n], StringComparison.OrdinalIgnoreCase) != 0);


                if (string.IsNullOrEmpty(rmd.columns[n].getOriginalTableNameString()))
                    row[SchemaTableColumn.BaseTableName] = DBNull.Value;
                else
                    row[SchemaTableColumn.BaseTableName] = rmd.columns[n].getOriginalTableNameString();


                if (string.IsNullOrEmpty(rmd.columns[n].getOriginalCatalogNameString()))
                    row[SchemaTableOptionalColumn.BaseCatalogName] = "PUBLIC";
                else
                    row[SchemaTableOptionalColumn.BaseCatalogName] = rmd.columns[n].getOriginalCatalogNameString();

                if (string.IsNullOrEmpty(rmd.columns[n].getOriginalSchemaNameString()))
                    row[SchemaTableColumn.BaseSchemaName] = DBNull.Value;
                else
                    row[SchemaTableColumn.BaseSchemaName] = rmd.columns[n].getOriginalSchemaNameString();


                if ((_commandBehavior & CommandBehavior.KeyInfo) != 0)
                {
                    Dictionary<string,string> uniqueCandiates = new Dictionary<string,string>();
                    string idx_name;
                    for (int i = 0; i < indexTbl.Rows.Count; i++)
                    {
                       idx_name = (string)indexTbl.Rows[i]["INDEX_NAME"];
                       if(!uniqueCandiates.ContainsKey(idx_name))
                       {
                           uniqueCandiates.Add(idx_name,(string)indexTbl.Rows[i]["COLUMN_NAME"]);
                       }
                       else
                       {
                           uniqueCandiates[idx_name] = String.Empty;
                       }
                    }

                    foreach (var idxn in uniqueCandiates.Keys)
                    {
                        if (String.IsNullOrEmpty(uniqueCandiates[idxn]))
                            continue;

                        if (String.Compare(rmd.columns[n].getOriginalNameString(),
                            uniqueCandiates[idxn], StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            row[SchemaTableColumn.IsUnique] = true;
                        }
                    }

                    for (int i = 0; i < columnTbl.Rows.Count; i++)
                    {
                        if (String.Compare(rmd.columns[n].getOriginalNameString(),
                          (string)columnTbl.Rows[i]["COLUMN_NAME"], StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            row[SchemaTableOptionalColumn.DefaultValue] =  columnTbl.Rows[i]["COLUMN_DEF"];

                            row[SchemaTableColumn.IsKey] = columnTbl.Rows[i]["IS_PRIMARY_KEY"];

                        }
                    }
                }

                tbl.Rows.Add(row);
            }

            tbl.AcceptChanges();
            tbl.EndLoadData();

            return tbl;
        }

        /// <summary>
        /// Retrieves the column as a string
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>string</returns>
        public override string GetString(int i)
        {
            CheckNull(i);

            object val = getColumnInType(i, SqlType.SQL_VARCHAR);
            if (val == null)
                throw new InvalidCastException();
            return (string)val;
        }

        /// <summary>
        /// Retrieves the column as an object corresponding to the underlying datatype of the column
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>object</returns>
        public override object GetValue(int columnIndex)
        {
            CheckValidRow();
            SessionInterface session = _command.Connection.InnerConnection.SessionProxy;
            return _nCurrent.getCurrent()[columnIndex] == null ? DBNull.Value :
                _rResult.metaData.columnTypes[columnIndex].convertSQLToCSharp(session,
                _nCurrent.getCurrent()[columnIndex]);
        }



        /// <summary>
        /// Retreives the values of multiple columns, up to the size of the supplied array
        /// </summary>
        /// <param name="values">The array to fill with values from the columns in the current resultset</param>
        /// <returns>The number of columns retrieved</returns>
        public override int GetValues(object[] values)
        {
            CheckValidRow();

            int nMax = iColumnCount;
            if (values.Length < nMax) nMax = values.Length;

            for (int n = 0; n < nMax; n++)
            {
                values[n] = GetValue(n);
            }

            return nMax;
        }

        /// <summary>
        /// Returns True if the resultset has rows that can be fetched
        /// </summary>
        public override bool HasRows
        {
            get
            {
                CheckClosed();
                return (_readingState != 1);
            }
        }

        /// <summary>
        /// Returns True if the data reader is closed
        /// </summary>
        public override bool IsClosed
        {
            get { return (_command == null); }
        }

        /// <summary>
        /// Returns True if the specified column is null
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>True or False</returns>
        public override bool IsDBNull(int i)
        {
            object val = GetValue(i);
            return val == DBNull.Value;
        }

        /// <summary>
        /// Moves to the next resultset in multiple row-returning SQL command.
        /// </summary>
        /// <returns>True if the command was successful and a new resultset is available, False otherwise.</returns>
        public override bool NextResult()
        {
            CheckClosed();

            if (_readingState == -1)
            {
                FetchResult();
                _rResult = _resultIn;
            }
            else if (_resultIn == null)
            {
                return false;
            }
            else if (_rResult == _resultIn && _generatedResult != null)
            {
                _rResult = _generatedResult;
            }
            else
            {
                return false;
            }           
          

            if (_rResult.isUpdateCount())
            {
                if (iUpdateCount == -1)
                    iUpdateCount = 0;

                iUpdateCount += _rResult.getUpdateCount();
                _readingState = 2;
                return NextResult();
            }
            else if (_rResult.isError())
            {
                _readingState = 2;
                EfzException.ThrowError(_rResult);
            }
            else if (_rResult.isData())
            {
                //iUpdateCount = -1;
                iColumnCount = _rResult.metaData.getColumnCount();
                _readingState = 0;
            }
            else
            {
                _readingState = 2;
            }

            return true;
        }

        public void CheckNull(int i)
        {
            if (IsDBNull(i))
                throw new ArgumentNullException();
        }


        /// <summary>
        /// Reads the next row from the resultset
        /// </summary>
        /// <returns>True if a new row was successfully loaded and is ready for processing</returns>
        public override bool Read()
        {
            CheckClosed();

            if (_readingState == 2)
                return false;

            // Have an empty resultset so exit with false
            if (_rResult == null || _rResult.getNavigator().isEmpty())
            {
                _readingState = 1;
                return false;
            }

            if (!bInit)
            {
                // The resultset has not been traversed, so set the cursor
                // to the first row (1)
                _nCurrent = _rResult.getNavigator();
                _nCurrent.getNext();
                bInit = true;
                iCurrentRow = 1;
            }
            else
            {
                if ((_commandBehavior & CommandBehavior.SingleRow) == 0)
                {
                    // The resultset has been traversed, if afterLast, return false
                    if (_nCurrent == null)
                    {
                        return false;
                    }

                    // On a valid row so go to next
                    _nCurrent.getNext();

                    iCurrentRow++;
                }
                else
                    _nCurrent = null;
            }

            // finally test to see if we are in an afterLast situation
            if (_nCurrent.isAfterLast())
            {

                // Yes, set the current row to after last and exit with false
                iCurrentRow = _rResult.getNavigator().size + 1;
                _readingState = 1; // Finished reading rows
                return false;
            }
            else
            {
                // Not afterLast, so success
                return true;
            }

        }

        /// <summary>
        /// Retrieve the count of records affected by an update/insert command.  Only valid once the data reader is closed!
        /// </summary>
        public override int RecordsAffected
        {
            get { return (iUpdateCount < 0) ? 0 : iUpdateCount; }
        }

        /// <summary>
        /// Indexer to retrieve data from a column given its name
        /// </summary>
        /// <param name="name">The name of the column to retrieve data for</param>
        /// <returns>The value contained in the column</returns>
        public override object this[string name]
        {
            get { return GetValue(GetOrdinal(name)); }
        }

        /// <summary>
        /// Indexer to retrieve data from a column given its i
        /// </summary>
        /// <param name="i">The index of the column to retrieve</param>
        /// <returns>The value contained in the column</returns>
        public override object this[int i]
        {
            get { return GetValue(i); }
        }

        /**
       * Internal result producer for command (sqlExecDirect mode). <p>
       *
       * @param sql a character sequence representing the SQL to be executed
       * @throws EffiProzDBException when a database access error occurs
       */
        private void FetchResult()
        {
            if ((_command.Parameters.Count != 0 || _command.CommandType == CommandType.StoredProcedure) 
                && _command._statement == null)
            {
                _command.Prepare();
            }

            if (_command._statement != null)
            {
                _resultIn = _command._statement.Execute(_command.Parameters, _command.MaxRows, _command.FetchSize);
                _generatedResult = _command._statement.GeneratedResult;
                return;
            }           
            else
            {
                EfzStatement tmpStatement = new EfzStatement(_command, _command.CommandText, false);
                if ((_commandBehavior & CommandBehavior.SchemaOnly) != 0)
                {                  
                    try
                    {
                       Result _resultIn_tmp = tmpStatement.Execute(1, 1);
                       
                        if(_resultIn_tmp.isError())
                            throw new Exception();

                        _resultIn = _resultIn_tmp;
                    }
                    catch (Exception)
                    {
                        _resultIn = tmpStatement.PrepareStatement();
                    }
                }
                else
                {
                    _resultIn = tmpStatement.Execute(_command.MaxRows, _command.FetchSize);
                }

                _generatedResult = tmpStatement.GeneratedResult;
                return;
            }         
        }      
    }
}
